package server.controller;

import server.comm.ConnectionHandler;
import server.storage.Storage;
import java.io.*;
import java.util.ArrayList;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import forum.Post;
import forum.Thread;
import messaage.*;

public class Controller {
	private Storage stor;
	private ArrayList<ConnectionHandler> loggedIns;

	public Controller() {
		this.stor = new Storage();
		this.loggedIns = new ArrayList<ConnectionHandler>();
	}

	/**
	 * This method restores the state of the forum from the XML file.
	 */
	public void readFromXML() {
		String ans = "";
		XStream xstream = new XStream(new DomDriver());
		try {
			BufferedReader in = new BufferedReader(new FileReader(
					"contentOfForum.txt"));
			String str;
			while ((str = in.readLine()) != null) {
				ans += str;
			}
			in.close();

			if (ans.equals("")) {
				this.stor = new Storage();
			} else {
				this.stor = (Storage) xstream.fromXML(ans);
				// update the statuses of all users for logged off.
				for (int i = 0; i < this.stor.getUsers().size(); i++) {
					this.stor.getUsers().get(i).logoff();
				}
			}
		} catch (IOException e) {
			System.err.println("Error: " + e.getMessage());
			this.stor = new Storage();
		}
	}

	/**
	 * This method saves the state of the forum into the XMl file.
	 */
	public void saveToXML() {
		XStream xstream = new XStream(new DomDriver());
		String xml = xstream.toXML(this.stor);
		try {
			FileWriter fstream = new FileWriter("contentOfForum.txt");
			BufferedWriter out = new BufferedWriter(fstream);
			out.write(xml);
			out.close();
		} catch (Exception excep) {
			System.err.println("Error: " + excep.getMessage());
		}
	}

	/**
	 * This method adds a new member to the forum.
	 * 
	 * @param regMsg
	 *            : the message which contains the data about the new member.
	 * @return an appropriate message according to results of registration.
	 */
	public Message getRegisterMsg(RegisterMsg regMsg) {
		int res = this.stor.addUser(regMsg.getUsername(),
				regMsg.getFirstName(), regMsg.getLastName(),
				regMsg.getPassword(), regMsg.getEmail(), regMsg.getGender());
		if (res == 0) {
			// return new OkMsg(regMsg.getUsername(),
			// "user was registered successfully");
			return new RegisteredMsg(regMsg.getUsername());
		}
		if (res == -1) {
			return new ErrorMsg(regMsg.getUsername(),
					"user was not registered successfully");
		}
		return new ErrorMsg(regMsg.getEmail(), "user with such username exists");
	}

	/**
	 * This method logs user in.
	 * 
	 * @param loginMsg
	 *            the message which contains the username and his password to
	 *            login.
	 * @return an appropriate message according to the results of logging in.
	 */
	public Message getLoginMsg(LoginMsg loginMsg) {
		int res = this.stor.login(loginMsg.getUsername(),
				loginMsg.getPassword());
		if (res == 0) {
			return new LoggedInMsg(loginMsg.getUsername(),
					this.stor.isUserModerator(loginMsg.getUsername()));
		}
		if (res == -1) {
			return new ErrorMsg(loginMsg.getUsername(),
					"passwords are not the same");
		}
		return new ErrorMsg(loginMsg.getUsername(), "user is not registered");
	}

	/**
	 * This method logs user out.
	 * 
	 * @param logoffMsg
	 *            : The message which contains a username of the user to log
	 *            out.
	 * @return an appropriate message according to the results of logging out.
	 */
	public Message getLogoffMsg(LogoutMsg logoffMsg) {
		int res = this.stor.logoff(logoffMsg.getUsername());
		if (res == 0) {
			return new LoggedOffMsg(logoffMsg.getUsername());
		}
		if (res == -1) {
			return new ErrorMsg(logoffMsg.getUsername(),
					"user was not logged off successfully");
		}
		return new ErrorMsg(logoffMsg.getUsername(), "user is not registered");
	}

	/**
	 * This method removes the specified user from the forum.
	 * 
	 * @param remUsrMsg
	 *            contains the names of the user to remove and user who removes.
	 * @return appropriate message according to the results of the removing.
	 */
	public Message getRemoveUser(RemoveUserMsg remUsrMsg) {
		int res = this.stor.removeUser(remUsrMsg.getUserToRemove(),
				remUsrMsg.getUsername());
		if (res == 0)
			return new RemovedUserMsg(remUsrMsg.getUsername(),
					remUsrMsg.getUserToRemove());
		if (res == -1)
			return new ErrorMsg(remUsrMsg.getUsername(), "removing user "
					+ remUsrMsg.getUserToRemove()
					+ " failed because this user is not registered");
		if (res == -2)
			return new ErrorMsg(remUsrMsg.getUsername(), "removing user "
					+ remUsrMsg.getUserToRemove()
					+ " failed because the user who removes is not logged in");
		if (res == -3)
			return new ErrorMsg(remUsrMsg.getUsername(), "removing user "
					+ remUsrMsg.getUserToRemove()
					+ " failed because the user who removes is not a moderator");
		// if (res == -4)
		return new ErrorMsg(remUsrMsg.getUsername(), "removing user "
				+ remUsrMsg.getUserToRemove()
				+ " failed because the user who removes is not registered");
	}

	/**
	 * this method adds a selected friend to the list of user's friend.
	 * 
	 * @param friendMsg
	 *            contains the usernames of both.
	 * @return an appropriate message according to the results.
	 */
	public Message getAddUserFriendMsg(AddFriendMsg friendMsg) {
		int res = this.stor.addUserFriend(friendMsg.getUsername(),
				friendMsg.getNameOfFriend());
		if (res == 0)
			// return new OkMsg(friendMsg.getUsername(), "friend "
			// + friendMsg.getNameOfFriend() + "was added successfully");
			return new AddedFriendMsg(friendMsg.getUsername(),
					friendMsg.getNameOfFriend());
		if (res == -1)
			return new ErrorMsg(friendMsg.getUsername(), "friend "
					+ friendMsg.getNameOfFriend()
					+ " already exists in friens' list");
		if (res == -2)
			return new ErrorMsg(friendMsg.getUsername(),
					"cannot add yorself to your friends' list");
		if (res == -3)
			return new ErrorMsg(friendMsg.getUsername(), "friend "
					+ friendMsg.getNameOfFriend() + " is not registered");
		if (res == -4)
			return new ErrorMsg(friendMsg.getUsername(),
					"user is not logged in");
		// if (res == -5)
		return new ErrorMsg(friendMsg.getUsername(), " is not registered");
	}

	/**
	 * This method removes a selected friend from the user's friends' list.
	 * 
	 * @param friendMsg
	 *            contains the usernames of both.
	 * @return an appropriate message according to the results.
	 */
	public Message getRemoveUserFriendMsg(RemoveFriendMsg friendMsg) {
		int res = this.stor.removeUserFriend(friendMsg.getUsername(),
				friendMsg.getNameOfFriend());
		if (res == 0)
			return new RemovedFriendMsg(friendMsg.getUsername(),
					friendMsg.getNameOfFriend());
		if (res == -1)
			return new ErrorMsg(friendMsg.getUsername(), "removing was failed");
		// if (res == -2)
		return new ErrorMsg(friendMsg.getUsername(),
				"User who removes must be logged in");
	}

	/**
	 * This method adds a new forum to the list of forums in the system.
	 * 
	 * @param msg
	 *            contains the name of the adder and the subject of new forum.
	 * @return an appropriate message according to the results.
	 */
	public Message getAddForumMsg(AddForumMsg msg) {
		int res = this.stor.addForum(msg.getSubject(), msg.getUsername());
		if (res >= 0) {
			return new AddedForumMsg(msg.getUsername(), Integer.toString(res),
					msg.getSubject(), "false");
		}
		if (res == -1)
			return new ErrorMsg(msg.getUsername(), "forum with subject "
					+ msg.getSubject() + " already exists");
		if (res == -2)
			return new ErrorMsg(msg.getUsername(),
					"User who adds forum must be logged in");
		if (res == -3)
			return new ErrorMsg(msg.getUsername(),
					"User who adds forum must be a moderator");
		if (res == -4)
			return new ErrorMsg(msg.getUsername(),
					"User who adds forum must be registered");
		// if (res == -5)
		return new ErrorMsg(msg.getUsername(),
				"subject of forum cannot be empty");
	}

	/**
	 * This method creates a new thread in the chosen forum.
	 * 
	 * @param msg
	 *            contains forumID - in to which forum to add a new thread
	 *            topicName of the new thread body of the new thread and the
	 *            author of the new thread.
	 * @return an appropriate message according to the results.
	 */
	public Message getCreateThread(CreateThreadMsg msg) {
		int res = this.stor.addThread(msg.getForumID(), msg.getTopicName(),
				msg.getBody(), msg.getUsername());
		if (res >= 0) {
			// return new OkMsg(msg.getUsername(),
			// "thread was added successfully");
			Thread t = this.stor.getThreadByID(res);
			return new CreatedThreadMsg(t, msg.getForumID() + "", false);
		}
		if (res == -1)
			return new ErrorMsg(msg.getUsername(),
					"adding thread failed. Maybe it already exists");
		if (res == -2)
			return new ErrorMsg(msg.getUsername(),
					"User who adds thread must be logged in");
		if (res == -3)
			return new ErrorMsg(msg.getUsername(),
					"User who adds thread must be registered");
		// if (res == -4)
		return new ErrorMsg(msg.getUsername(), "Subject cannot be empty");
	}

	/**
	 * Adds a post to the selected thread and the selected forum
	 * 
	 * @param msg
	 *            contains all the necessary information about where we should
	 *            add a new post.
	 * @return an appropriate message according to the results.
	 */
	public Message getAddPostMsg(AddPostMsg msg) {
		int res = this.stor.addPost(msg.getForumID(), msg.getPostID(),
				msg.getTopicName(), msg.getBody(), msg.getUsername());
		//System.out.println("msg.getPostId: "+msg.getPostID()+" msg.getTopicName: "+msg.getTopicName());
		if (res >= 0) {
			String uName = msg.getUsername();
//			int tId = msg.getPostID();
//			Post parent = this.stor.getForums().getPost(tId);
			Post toAdd = this.stor.getForums().getPost(res);

			// Send message to friends
			ArrayList<String> friends = this.stor.getFriendsOfUser(uName);
			for (int i = 0; i < friends.size(); i++) {
				ConnectionHandler ch = this
						.findConnectionHandlerByUsername(friends.get(i));
				if (ch != null) {
					AddedPostMsg tMsg = new AddedPostMsg(ch.getUsername(),
							true, msg.getForumID() + "", toAdd.getID() + "",
							toAdd.getSubject(), toAdd.getAuthor(), toAdd.getDate(),
							toAdd.getParent(), toAdd.getContent());
					ch.send(tMsg.toString());
				}
			}

			// Send message to viewers
			ArrayList<String> viewers = this.stor.getParentThread(toAdd.getID())
					.getCurrentViewers();
			System.out.println(this.stor.getParentThread(toAdd.getID()));
			for (int i = 0; i < viewers.size(); i++) {
				ConnectionHandler ch = this
						.findConnectionHandlerByUsername(viewers.get(i));
				if (ch != null) {
					AddedPostMsg tMsg = new AddedPostMsg(ch.getUsername(),
							true, msg.getForumID() + "", toAdd.getID() + "",
							toAdd.getSubject(), toAdd.getAuthor(), toAdd.getDate(),
							toAdd.getParent(), toAdd.getContent());
					ch.send(tMsg.toString());
				}
			}

			// Send message to author
			String author = toAdd.getAuthor();
			ConnectionHandler ch = this.findConnectionHandlerByUsername(author);
			if (ch != null) {

				AddedPostMsg tMsg = new AddedPostMsg(ch.getUsername(), false,
						msg.getForumID() + "", toAdd.getID() + "", toAdd.getSubject(),
						toAdd.getAuthor(), toAdd.getDate(), toAdd.getParent(),
						toAdd.getContent());
				ch.send(tMsg.toString());
			}

			return new AddedPostMsg(ch.getUsername(), false, msg.getForumID()
					+ "", toAdd.getID() + "", toAdd.getSubject(), toAdd.getAuthor(),
					toAdd.getDate(), toAdd.getParent(), toAdd.getContent());
		}
		if (res == -1)
			return new ErrorMsg(msg.getUsername(), "adding post failed.");
		if (res == -2)
			return new ErrorMsg(msg.getUsername(),
					"User who adds post must be logged in");
		if (res == -3)
			return new ErrorMsg(msg.getUsername(),
					"User who adds post must be registered");
		// if (res == -4)
		return new ErrorMsg(msg.getUsername(), "Subject cannot be empty");
	}

	/**
	 * 
	 * @param msg
	 * @return appropriate msg
	 */
	public Message getEditPostMsg(EditPostMsg msg) {
		int res = this.stor.editPost(msg.getUsername(), msg.getForumID(),
				msg.getPostID(), msg.getContent());
		System.out.println("Res is: "+ res);
		if (res >= 0) {
			Post p = this.stor.getPostByID(msg.getPostID());
			return new EditedPostMsg(false, msg.getForumID()+"", p.getID()+"", p.getDate(), p.getContent());
			
//			return new EditedPostMsg(msg.getUsername(), p, msg.getForumID()
//					+ "", false);
		}
		if (res == -1)
			return new ErrorMsg(
					msg.getUsername(),
					" user has no permeations to edit this post or forum/thread not exist or such post with new content with same subject and author is already exist");
		if (res == -2)
			return new ErrorMsg(msg.getUsername(),
					"User who edits post must be logged in");
		if (res == -3)
			return new ErrorMsg(msg.getUsername(),
					"User who edits post must be registered");
		// if (res == -4)
		return new ErrorMsg(msg.getUsername(), "Content cannot be empty");
	}

	/**
	 * This method removes a specified post.
	 * 
	 * @param msg
	 *            contains the information about a post to remove.
	 * @return an appropriate message according to the results.
	 */
	public Message getRemovePostMsg(RemovePostMsg msg) {
		int res = this.stor.removePost(msg.getUsername(), msg.getForumID(),
				msg.getPostID());
		if (res == 0)
			return new OkMsg(msg.getUsername(), "post was removed successfully");
		if (res == -1)
			return new ErrorMsg(msg.getUsername(),
					"User must be logged in in order to remove post");
		if (res == -2)
			return new ErrorMsg(msg.getUsername(),
					"User does not have a suitable authority to remove post");
		if (res == -3)
			return new ErrorMsg(msg.getUsername(),
					"Remove post failed. Post does not exist");
		// if (res == -5)
		return new ErrorMsg(msg.getUsername(),
				"Remove post failed. Forum does not exist");
	}

	/**
	 * This method removes the specified thread from the forum.
	 * 
	 * @param msg
	 *            contains all the necessary information about the thread to
	 *            remove.
	 * @return an appropriate message according to the results.
	 */
	public Message getRemoveThreadMsg(RemoveThreadMsg msg) {
		int res = this.stor.removeThread(msg.getUsername(), msg.getForumID(),
				msg.getThreadID());
		if (res == 0)
			return new OkMsg(msg.getUsername(),
					"Thread was removed successfully");
		if (res == -1)
			return new ErrorMsg(msg.getUsername(),
					"User must be logged in in order to remove thread");
		if (res == -2)
			return new ErrorMsg(msg.getUsername(),
					"User does not have a suitable authority to remove thread");
		if (res == -3)
			return new ErrorMsg(msg.getUsername(),
					"Remove thread failed. Thread does not exist");
		// if (res == -4)
		return new ErrorMsg(msg.getUsername(),
				"Remove thread failed. Forum does not exist");
	}

	/**
	 * This method removes the selected forum from the list of forums.
	 * 
	 * @param msg
	 *            contains the information about a forum to remove.
	 * @return an appropriate message according to the results.
	 */
	public Message getRemoveForumMsg(RemoveForumMsg msg) {
		int res = this.stor.removeForum(msg.getUsername(), msg.getForumID());
		if (res == 0)
			return new OkMsg(msg.getUsername(),
					"Forum was removed successfully");
		if (res == -1)
			return new ErrorMsg(msg.getUsername(),
					"User must be logged in in order to remove forum");
		if (res == -2)
			return new ErrorMsg(msg.getUsername(),
					"User must be a moderator for removing forum");
		// if (res == -3)
		return new ErrorMsg(msg.getUsername(),
				"Remove forum failed. Forum does not exist");
	}

	/**
	 * 
	 * @param msg
	 * @return OK message which contains the list of forums or error message if
	 *         the user is not logged in.
	 */
	public Message ForumsListReq(GetListOfForumsMsg msg) {
		String listOfForums = this.stor.getListOfForums(msg.getUsername());
		if (listOfForums.isEmpty())
			return new ErrorMsg(msg.getUsername(),
					"User must be logged in in order to see the list of forums");
		return new GotListOfForumsMsg(msg.getUsername(), this.stor.getForums()
				.getForums());
	}

	/**
	 * 
	 * @param msg
	 * @return OK message with the topics of threads in the requested forum and
	 *         error message if the user is not logged in.
	 */
	public Message getForumsTopicsMsg(GetForumTopicsMsg msg) {
		String topicsOfForum = this.stor.getThreadsTopicsOfForum(
				msg.getUsername(), msg.getForumID());
		if (topicsOfForum.isEmpty()) {
			return new ErrorMsg(msg.getUsername(),
					"User must be logged in in order to see the list of topics of the forum");
		}
		return new GotTopicsOfForumMsg(topicsOfForum);
	}

	/**
	 * 
	 * @param msg
	 * @return OK message a list of the threads with their posts in the
	 *         requested forum or error message if the user is not logged in.
	 */
	public Message getThreadOfForum(GetThreadMsg msg) {
		String threadsOfForum = this.stor.getThreadsOfTopic(msg.getUsername(),
				msg.getForumID(), msg.getThreadID());
		if (threadsOfForum.isEmpty()) {
			return new ErrorMsg(msg.getUsername(),
					"User must be logged in in order to see the list of threads of the forum");
		}
		int tId = msg.getThreadID();
		forum.Thread t = this.stor.getThreadByID(tId);
		t.addViewer(msg.getUsername());
		this.stor.addViewerByName(msg.getUsername(), t);
		GotThreadMsg gtm = new GotThreadMsg(t.showAllTread());
		gtm.setForumID(msg.getForumID() + "");
		return gtm;
		// return new GotThreadMsg(msg.getUsername(), msg.getForumID()+"", t);
	}

	/**
	 * @param msg
	 *            : the message that asks for user's friends
	 * @return GotFriendsListMsg with the list of user's friends if the user is
	 *         registered and logged in or ErrorMsg if the user is not
	 *         registered or logged in
	 */
	public Message getFriendsList(GetFriendsListMsg msg) {

		ArrayList<String> friends = this.stor.getFriendsOfUser(msg
				.getUsername());
		String ans = "";
		for (int i = 0; i < friends.size(); i++)
			ans += "friend:" + friends.get(i) + "\n";
		ans += "\0";
		if (ans.isEmpty()) {
			return new ErrorMsg(msg.getUsername(),
					"User must be registered and logged in in order to see his friend list");
		}
		return new GotFriendsListMsg(ans);
	}

	/**
	 * @return the container of the list of forums and the list of users in the
	 *         system.
	 */
	public Storage getStorage() {
		return this.stor;
	}

	public void addLoggedIn(ConnectionHandler ch) {
		if (!this.loggedIns.contains(ch)) {
			this.loggedIns.add(ch);
		}
	}

	public void removeLoggedIn(ConnectionHandler ch) {
		if (this.loggedIns.contains(ch)) {
			this.loggedIns.remove(ch);
		}
	}

	// /////// Private Funcs /////////
	private ConnectionHandler findConnectionHandlerByUsername(String user) {
		ConnectionHandler ans = null;
		for (int i = 0; i < this.loggedIns.size(); i++) {
			if (this.loggedIns.get(i).getUsername().equals(user)) {
				ans = this.loggedIns.get(i);
			}
		}
		return ans;
	}
}
